package engine.util.collada;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import engine.gameObject.GameObject;
import engine.gameObject.model.Model;
import engine.util.texture.TextureLoader;
import static org.lwjgl.opengl.GL11.*;

public class ColladaLoader {

	private static final HashMap<String, GameObject> cache = new HashMap<String, GameObject>();
	
	public static GameObject importFile(String fileName) {
		return importFile(fileName, 3);
	}
	
	/**
	 * This function is used to import a collada file. 
	 * @param fileName
	 * @param collisionLevels The higher this value, collisions are detected with more accuracy due to a lack of performance. If set to less than zero, a default value of 3 is used. 
	 * @return
	 */
	public static GameObject importFile(String fileName, int collisionLevels) {
		if(!cache.containsKey(fileName)) {
			cacheFile(fileName, collisionLevels);
		}
		
		return cache.get(fileName).copy();
	}
	
	private static void cacheFile(String fileName, int collisionLevels) {
		if(collisionLevels < 0) {
			collisionLevels = 3;
		}
		
		//find file and instantiate everything for the xml-reader. 
		File file = new File(fileName);
		InputStream inputStream = null;
		if(!file.exists()) {
			URL url = ColladaLoader.class.getClassLoader().getResource(fileName);
			
			if(url != null) {
				//TODO fix that loading via url
				inputStream = TextureLoader.class.getClassLoader().getResourceAsStream(fileName);
				String path = url.getPath();
				if(path.startsWith("file:")) {
					path = path.substring("file:".length());
				}
				
				try {
					file = new File(url.toURI());
				} catch (URISyntaxException e) {
					e.printStackTrace();
				}
				
				System.out.println("here we go: ");
				System.out.println(path);
				System.out.println(fileName);
				System.out.println(url.getPath());
				System.out.println(file.exists());
				System.out.println(file.getAbsolutePath());
			} else {
				System.err.println("File not found: " + fileName);
				return;
			}
		} else {
			try {
				inputStream = new FileInputStream(file);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
		
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = null;
		try {
			dBuilder = dbFactory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return;
		}
		Document doc = null;
		try {
			doc = dBuilder.parse(inputStream);
		} catch (SAXException s) {
			s.printStackTrace();
			return;
		} catch (IOException e) {
			e.printStackTrace();
			return;
		}
		
		//this matrix stores the inital coordinate system of the model
		float[][] matrix = getMatrix(doc);
		
		//extract images from the file and load them
		ArrayList<ColladaImage> images = loadImages(doc);
		
		//extract materials out of the file
		ArrayList<ColladaMaterial> materials = loadMaterials(file, doc, images);
		
		//create a new model and fill it with data
		ArrayList<GameObject> geometries = loadGeometries(doc, matrix, materials, collisionLevels);
		
		//load the scene and connect all the elements
		loadScenes(doc);
		
		if(geometries.size() == 0) {
			System.err.println("There is no stuff in this model: " + fileName);
			return;
		}
		
		GameObject scene = null;
		if(geometries.size() == 1) {
			scene = geometries.get(0);
		} else {
			scene = new GameObject();
			for(GameObject g: geometries) {
				scene.addChild(g);
			}
			scene.setId(fileName);
		}
		
		cache.put(fileName, scene);
	}
	
	private static float[][] getMatrix(Document document) {
		float[][] mat = new float[][]{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
		
		Element asset = (Element)document.getElementsByTagName("asset").item(0);
		
		NodeList upAxisNodeList = asset.getElementsByTagName("up_axis");
		
		if(upAxisNodeList.getLength() > 0) {
			String upAxis = upAxisNodeList.item(0).getTextContent();
			
			if(upAxis.equals("X_UP")) {
				mat = new float[][]{{0, -1, 0}, {1, 0, 0}, {0, 0, 1}};
			} else if(upAxis.equals("Z_UP")) {
				mat = new float[][]{{1, 0, 0}, {0, 0, 1}, {0, -1, 0}};
			} else {
				//do nothing
			}
		}
		
		return mat;
	}
	
	private static ArrayList<ColladaImage> loadImages(Document document) {
		ArrayList<ColladaImage> images = new ArrayList<ColladaImage>();
		
		//find the library containting the images
		NodeList libraryImageList = document.getElementsByTagName("library_images");
		
		if(libraryImageList.getLength() > 0) {
			//find images within the library
			NodeList imageList = ((Element)libraryImageList.item(0)).getElementsByTagName("image");
			
			for(int i = 0; i < imageList.getLength(); i++) {
				//retrieve the image from the list
				Element image = (Element)imageList.item(i);
				
				String imageID = image.getAttribute("id");
				String imageURL = image.getElementsByTagName("init_from").item(0).getTextContent();
				
				images.add(new ColladaImage(imageID, imageURL));
			}
		}
		
		return images;
	}
	
	private static ArrayList<ColladaMaterial> loadMaterials(File file, Document document, ArrayList<ColladaImage> images) {
		ArrayList<ColladaMaterial> materials = new ArrayList<ColladaMaterial>();
		
		//find the library with the materials
		NodeList libraryEffectsList = document.getElementsByTagName("library_effects");
		
		if(libraryEffectsList.getLength() > 0) {
			//find any effect within the library
			NodeList effectList = ((Element)libraryEffectsList.item(0)).getElementsByTagName("effect");
			
			//loop over the effects
			for(int i = 0; i < effectList.getLength(); i++) {
				//use these lists to store the created textures and surfaces
				ArrayList<ColladaSurface> surfaces = new ArrayList<ColladaSurface>();
				ArrayList<ColladaTexture> textures = new ArrayList<ColladaTexture>();
				
				Element effect = (Element)effectList.item(i);
				String effectID = effect.getAttribute("id");
				
				//check, if the tag with "profile_COMMON" exists. 
				NodeList profileCommonList = effect.getElementsByTagName("profile_COMMON");
				
				if(profileCommonList.getLength() > 0) {
					//at first, extract the new param attributes
					NodeList newParamList = ((Element)profileCommonList.item(0)).getElementsByTagName("newparam");
					
					for(int j = 0; j < newParamList.getLength(); j++) {
						Element newParam = (Element)newParamList.item(j);
						
						//find the surface, which connects texture and image
						NodeList surfaceList = newParam.getElementsByTagName("surface");
						if(surfaceList.getLength() > 0) {
							Element surface = (Element)surfaceList.item(0);
							
							if(surface.getAttribute("type").equals("2D")) {
								NodeList initFromList = surface.getElementsByTagName("init_from");
								
								if(initFromList.getLength() > 0) {
									String sourceImageID = initFromList.item(0).getTextContent();
									
									surfaces.add(new ColladaSurface(newParam.getAttribute("sid"), sourceImageID));
								} else {
									System.out.println("Images can only processed with init_from tags");
								}
							} else {
								System.out.println("Non 2D textures are not supported");
							}
						}
						
						//find sampler2D, which contains the texture information how to wrap etc. 
						NodeList sampler2DList = newParam.getElementsByTagName("sampler2D");
						if(sampler2DList.getLength() > 0) {
							Element sampler2D = (Element)sampler2DList.item(0);
							
							String sourceSurfaceID = sampler2D.getElementsByTagName("source").item(0).getTextContent();
							
							int wrapS = GL_REPEAT;
							NodeList wrapSList = sampler2D.getElementsByTagName("wrap_s");
							if(wrapSList.getLength() > 0) {
								wrapS = getTextureWrap(wrapSList.item(0).getTextContent());
							}
							
							int wrapT = GL_REPEAT;
							NodeList wrapTList = sampler2D.getElementsByTagName("wrap_t");
							if(wrapTList.getLength() > 0) {
								wrapT = getTextureWrap(wrapTList.item(0).getTextContent());
							}
							
							int minFilter = GL_LINEAR;
							NodeList minFilterList = sampler2D.getElementsByTagName("minfilter");
							if(minFilterList.getLength() > 0) {
								minFilter = getTextureFilter(minFilterList.item(0).getTextContent());
							}
							
							int magFilter = GL_LINEAR;
							NodeList magFilterList = sampler2D.getElementsByTagName("magfilter");
							if(magFilterList.getLength() > 0) {
								magFilter = getTextureFilter(magFilterList.item(0).getTextContent());
							}
							
							boolean mipmapped = minFilter != GL_LINEAR && minFilter != GL_NEAREST && magFilter != GL_LINEAR && magFilter != GL_NEAREST;
							
							String path = getURL(file, findImageByID(findSurfaceByID(sourceSurfaceID, surfaces).getSourceImageId(), images).getPath());
							textures.add(new ColladaTexture(newParam.getAttribute("sid"), path, wrapS, wrapT, minFilter, magFilter, mipmapped));
						}
					}
					
					//extract techniques from the profile
					Element technique = (Element)((Element)profileCommonList.item(0)).getElementsByTagName("technique").item(0);
					
					//look for the phong element
					NodeList phongList = technique.getElementsByTagName("phong");
					NodeList blinnList = technique.getElementsByTagName("blinn");
					
					if(phongList.getLength() > 0) {
						extractMaterials(effectID, (Element)phongList.item(0), textures, materials);
					} else if(blinnList.getLength() > 0) {
						extractMaterials(effectID, (Element)blinnList.item(0), textures, materials);
					} else {
						System.out.println("Sorry, currently only phong or blinn shading is supported");
					}
				} else {
					System.out.println("There is a not common material. This is currently not supported");
				}
			}
		}
		
		//find library materials for the correct names
		NodeList libraryMaterialsList = document.getElementsByTagName("library_materials");
		
		if(libraryMaterialsList.getLength() > 0) {
			Element libraryMaterial = (Element)libraryMaterialsList.item(0);
			
			NodeList materialList = libraryMaterial.getElementsByTagName("material");
			
			//loop over the materials and replace the old id's by the new referenced id's
			for(int i = 0; i < materialList.getLength(); i++) {
				Element material = (Element)materialList.item(i);
				String id = material.getAttribute("id");
				
				Element instanceEffect = (Element)material.getElementsByTagName("instance_effect").item(0);
				findMaterialById(instanceEffect.getAttribute("url").replace("#", ""), materials).setId(id);
			}
		}
		
		//correct id's by the names for instantiation
		NodeList materialInstanceList = document.getElementsByTagName("instance_material");
		
		for(int i = 0; i < materialInstanceList.getLength(); i++) {
			Element materialInstance = (Element)materialInstanceList.item(i);
			findMaterialById(materialInstance.getAttribute("target").replace("#", ""), materials).setId(materialInstance.getAttribute("symbol"));
		}
		
		return materials;
	}
	
	private static void extractMaterials(String effectID, Element effectElement, ArrayList<ColladaTexture> textures, ArrayList<ColladaMaterial> materials) {
		
		//reserve memory for the materials
		float[] ambient = null;
		float[] diffuse = null;
		float[] specular = null;
		float[] emission = null;
		float shininess = 0;
		
		String textureID = null;
		
		//try ambient
		NodeList ambientList = effectElement.getElementsByTagName("ambient");
		if(ambientList.getLength() > 0) {
			ambient = extractColor((Element)ambientList.item(0));
		}
		
		//try diffuse
		NodeList diffuseList = effectElement.getElementsByTagName("diffuse");
		if(diffuseList.getLength() > 0) {
			diffuse = extractColor((Element)diffuseList.item(0));
			textureID = extractTexture((Element)diffuseList.item(0));
		}
		
		//try specular
		NodeList specularList = effectElement.getElementsByTagName("specular");
		if(specularList.getLength() > 0) {
			specular = extractColor((Element)specularList.item(0));
		}
		
		//try emission
		NodeList emissionList = effectElement.getElementsByTagName("emission");
		if(emissionList.getLength() > 0) {
			emission = extractColor((Element)emissionList.item(0));
		}
		
		//try shininess
		NodeList shininessList = effectElement.getElementsByTagName("shininess");
		if(shininessList.getLength() > 0) {
			shininess = extractFloat((Element)shininessList.item(0));
		}
		
		materials.add(new ColladaMaterial(effectID, ambient, diffuse, specular, emission, shininess, findTextureByID(textureID, textures)));
	}
	
	private static float[] extractColor(Element element) {
		float[] buffer = new float[]{0, 0, 0, 1};
		
		NodeList colorList = element.getElementsByTagName("color");
		if(colorList.getLength() > 0) {
			String[] split = colorList.item(0).getTextContent().split(" ");
			for(int i = 0; i < Math.min(split.length, 4); i++) {
				buffer[i] = Float.parseFloat(split[i]);
			}
		}
		
		return buffer;
	}
	
	private static float extractFloat(Element element) {
		return Float.parseFloat(element.getElementsByTagName("float").item(0).getTextContent());
	}
	
	private static String extractTexture(Element element) {
		String textureID = null;
		NodeList textureList = element.getElementsByTagName("texture");
		if(textureList.getLength() > 0) {
			textureID = ((Element)textureList.item(0)).getAttribute("texture");
		}
		return textureID;
	}
	
	private static ArrayList<GameObject> loadGeometries(Document document, float[][] matrix, ArrayList<ColladaMaterial> materials, int collisionLevels) {
		//create list with geometries
		ArrayList<GameObject> geometries = new ArrayList<GameObject>();
		
		//find the library with geometries
		NodeList libraryGeometriesList = document.getElementsByTagName("library_geometries");
		
		if(libraryGeometriesList.getLength() > 0) {
			//find any geometry tag within the library
			NodeList nodeList = ((Element)libraryGeometriesList.item(0)).getElementsByTagName("geometry");
			
			//loop over the geometry elements
			for(int i = 0; i < nodeList.getLength(); i++) {
				Element geometry = (Element)nodeList.item(i);
				String geometryID = geometry.getAttribute("id");
				
				//find meshes within the geometry
				NodeList meshes = geometry.getElementsByTagName("mesh");
				
				//check, if it exists
				if(meshes.getLength() > 0) {
					GameObject mesh = loadMesh((Element)meshes.item(0), matrix, materials, collisionLevels);
					mesh.setId(geometryID);
					geometries.add(mesh);
				} else {
					System.out.println("Sorry, currently only usual meshes are supported");
				}
			}
		}
		
		return geometries;
	}
	
	private static GameObject loadMesh(Element meshElement, float[][] mat, ArrayList<ColladaMaterial> materials, int collisionLevels) {
		GameObject mesh = new GameObject();
		
		//at first locate all the sources and load them
		NodeList sourceList = meshElement.getElementsByTagName("source");
		Source[] sources = new Source[sourceList.getLength()];
		
		for(int i = 0; i < sourceList.getLength(); i++) {
			sources[i] = loadSoure((Element)sourceList.item(i));
		}
		
		//identify vertices
		Element verticesElement = (Element)meshElement.getElementsByTagName("vertices").item(0);
		String verticesID = verticesElement.getAttribute("id");
		NodeList inputs = verticesElement.getElementsByTagName("input");
		for(int i = 0; i < inputs.getLength(); i++) {
			Element inputElement = (Element)inputs.item(i);
			
			if(inputElement.getAttribute("semantic").equals("POSITION")) {
				Source vertices = findSourceById(inputElement.getAttribute("source").replace("#", ""), sources);
				vertices.id = verticesID;
			}
		}
		
		//extract polylist
		NodeList polylistList = meshElement.getElementsByTagName("polylist");
		for(int polylistCount = 0; polylistCount < polylistList.getLength(); polylistCount++) {
			//reserve variables for the specific buffers
			IntBuffer indexBuffer = null;
			FloatBuffer verticesBuffer = null;
			FloatBuffer normalsBuffer = null;
			FloatBuffer texCoordBuffer = null;
			
			//reserve variables for indexing
			int totalOffsets = 0;
			int vertexOffset = -1;
			int normalOffset = -1;
			int texCoordOffset = -1;
			
			Element polylist = (Element)polylistList.item(polylistCount);
			ColladaMaterial material = null;
			if(polylist.hasAttribute("material")) {
				material = findMaterialById(polylist.getAttribute("material").replace("#", ""), materials);
			}
			if(material == null) {
				material = new ColladaMaterial("", null, null, null, null, 0, null);
			}
			
			//set vertices, normals and texCoords
			NodeList polylistInputs = polylist.getElementsByTagName("input");
			
			for(int i = 0; i < polylistInputs.getLength(); i++) {
				Element inputElement = (Element)polylistInputs.item(i);
				
				String semantic = inputElement.getAttribute("semantic");
				String source = inputElement.getAttribute("source").replace("#", "");
				int offset = Integer.parseInt(inputElement.getAttribute("offset"));
				
				if(semantic.equals("VERTEX")) {
					verticesBuffer = findSourceById(source, sources).data;
					vertexOffset = offset;
					multAll(verticesBuffer, mat);
				} else if(semantic.equals("NORMAL")) {
					normalsBuffer = findSourceById(source, sources).data;
					normalOffset = offset;
					multAll(normalsBuffer, mat);
				} else if(semantic.equals("TEXCOORD")) {
					if(texCoordBuffer != null) {
						throw new NullPointerException("Only one texture layer is currently allowed. ");
					}
					texCoordBuffer = findSourceById(source, sources).data;
					texCoordOffset = offset;
				}
				
				totalOffsets++;
			}
			
			//correct total offsets, if two ore more offset are equal
			if(vertexOffset >= 0 && vertexOffset == normalOffset) {
				totalOffsets--;
				normalOffset = -1;
			}
			
			if(vertexOffset >= 0 && vertexOffset == texCoordOffset) {
				totalOffsets--;
				texCoordOffset = -1;
			}
			
			if(normalOffset >= 0 && normalOffset == texCoordOffset) {
				totalOffsets--;
				texCoordOffset = -1;
			}
			
			int[] offsetArray = new int[totalOffsets];
			int[] countsArray = new int[totalOffsets];
			FloatBuffer[] bufferArray = new FloatBuffer[totalOffsets];
			int num = 0;
			if(vertexOffset >= 0) {
				offsetArray[num] = vertexOffset;
				countsArray[num] = 3;
				bufferArray[num++] = verticesBuffer;
			}
			
			if(normalOffset >= 0) {
				offsetArray[num] = normalOffset;
				countsArray[num] = 3;
				bufferArray[num++] = normalsBuffer;
			}
			
			if(texCoordOffset >= 0) {
				offsetArray[num] = texCoordOffset;
				countsArray[num] = 2;
				bufferArray[num++] = texCoordBuffer;
			}
			
			//set indices
			NodeList vcountList = polylist.getElementsByTagName("vcount");
			
			if(vcountList.getLength() > 0) {
				String[] vcount = vcountList.item(0).getTextContent().split(" ");
				int[] counts = new int[vcount.length];
				int totalTriangles = 0;
				
				for(int i = 0; i < vcount.length; i++) {
					counts[i] = Integer.parseInt(vcount[i]);
					totalTriangles += counts[i] - 2;
				}
				
				//check, if there exists a list how the indices of the different arrays are connected
				NodeList pList = polylist.getElementsByTagName("p");
				
				if(pList.getLength() > 0) {
					String[] split = pList.item(0).getTextContent().trim().split(" ");
					int[][] pListNumbers = new int[split.length / totalOffsets][totalOffsets];
					
					for(int i = 0; i < split.length; i += totalOffsets) {
						for(int j = 0; j < totalOffsets; j++) {
							pListNumbers[i / totalOffsets][offsetArray[j]] = Integer.parseInt(split[i + j]);
						}
						//pListNumbers[i / 2][0] = Integer.parseInt(split[i]);
						//pListNumbers[i / 2][1] = Integer.parseInt(split[i + 1]);
					}
					
//					System.out.println("oldlength: " + bufferArray[0].capacity());
					detectCriticalPairs(pListNumbers, bufferArray, countsArray);
//					System.out.println("newlength: " + bufferArray[0].capacity());
					
					indexBuffer = BufferUtils.createIntBuffer(totalTriangles * 3);
					FloatBuffer[] newBuffers = new FloatBuffer[bufferArray.length - 1];
					for(int i = 0; i < newBuffers.length; i++) {
						newBuffers[i] = BufferUtils.createFloatBuffer(bufferArray[i + 1].capacity());
					}
					//FloatBuffer newNormalBuffer = BufferUtils.createFloatBuffer(normalsBuffer.capacity());
					
					int base = 0;
					for(int i = 0; i < counts.length; i++) {
//						System.out.print(counts[i] + " ");
						for(int j = 2; j < counts[i]; j++) {
							indexBuffer.put(pListNumbers[base][0]);
							indexBuffer.put(pListNumbers[base + j - 1][0]);
							indexBuffer.put(pListNumbers[base + j][0]);
							
							for(int k = 0; k < newBuffers.length; k++) {
								copyBuffer(bufferArray[k + 1], newBuffers[k], pListNumbers[base][1 + k], pListNumbers[base][0], countsArray[k + 1]);
								copyBuffer(bufferArray[k + 1], newBuffers[k], pListNumbers[base + j - 1][1 + k], pListNumbers[base + j - 1][0], countsArray[k + 1]);
								copyBuffer(bufferArray[k + 1], newBuffers[k], pListNumbers[base + j][1 + k], pListNumbers[base + j][0], countsArray[k + 1]);
							}
							
//							copyBuffer(normalsBuffer, newNormalBuffer, pListNumbers[i * 4][1], pListNumbers[i * 4][0]);
//							copyBuffer(normalsBuffer, newNormalBuffer, pListNumbers[i * 4 + j - 1][1], pListNumbers[i * 4 + j - 1][0]);
//							copyBuffer(normalsBuffer, newNormalBuffer, pListNumbers[i * 4 + j][1], pListNumbers[i * 4 + j][0]);
						}
						base += counts[i];
					}
					
					for(int i = 0; i < newBuffers.length; i++) {
						bufferArray[i + 1] = newBuffers[i];
					}
					
					num = 0;
					if(vertexOffset >= 0) {
						verticesBuffer = bufferArray[num++];
					}
					
					if(normalOffset >= 0) {
						normalsBuffer = bufferArray[num++];
					}
					
					if(texCoordOffset >= 0) {
						texCoordBuffer = bufferArray[num++];
					}
					//normalsBuffer = newNormalBuffer;
				} else {
					indexBuffer = BufferUtils.createIntBuffer(totalTriangles * 3);
					
					for(int i = 0; i < counts.length; i++) {
						for(int j = 2; j < counts[i]; j++) {
							indexBuffer.put(i * 4);
							indexBuffer.put(i * 4 + j - 1);
							indexBuffer.put(i * 4 + j);
						}
					}
				}
				
				//add texture to polylist
				Model model = null;
				if(material.getTexture() != null) {
					model = new Model(indexBuffer, verticesBuffer, normalsBuffer, texCoordBuffer, material.toMaterial(), material.getTexture().getRenderID(), collisionLevels);
				} else {
					model = new Model(indexBuffer, verticesBuffer, normalsBuffer, texCoordBuffer, material.toMaterial(), 0, collisionLevels);
				}
				mesh.addChild(model);
			}
		} //end loop of polylist extraction
		
		//return the created mesh
		if(mesh.getChildren().size() == 1) {
			return mesh.getChildren().get(0);
		} else {
			return mesh;
		}
	}
	
	private static void copyBuffer(FloatBuffer oldBuffer, FloatBuffer newBuffer, int oldPos, int newPos, int count) {
		for(int i = 0; i < count; i++) {
			newBuffer.put(newPos * count + i, oldBuffer.get(oldPos * count + i));
		}
	}
	
	@SuppressWarnings("unchecked")
	private static void detectCriticalPairs(int[][] pData, FloatBuffer[] buffers, int[] countsArray) {
		int numberAdded = 0;
		
		//create an array of lists, where the values are stored for resolving critical pairs. 
		ArrayList<Float>[] addingToBuffers = (ArrayList<Float>[])new ArrayList[3];
		for(int i = 0; i < addingToBuffers.length; i++) {
			addingToBuffers[i] = new ArrayList<Float>();
		}
		
		for(int i = 0; i < pData.length; i++) {
			for(int j = i + 1; j < pData.length; j++) {
				//check if the first two values are identical. if yes, it could be a critical pair, if one of the following values are unequal
				if(pData[i][0] == pData[j][0]) {
					for(int k = 1; k < pData[i].length; k++) {
						//check if one of the following values is unequal
						if(pData[i][k] != pData[j][k]) {
							//System.out.println("critical pair found: (" + i + "|" + j + ") by " + (k == 1 ? "normals" : "texcoords"));
							//found a critical pair
							
							for(int l = 0; l < buffers.length; l++) {
								//copy the old values into new positions
								for(int m = 0; m < countsArray[l]; m++) {
									addingToBuffers[l].add(buffers[l].get(pData[i][l] * countsArray[l] + m));
								}
								//change positions on the indexing array
								pData[i][l] = buffers[l].capacity() / countsArray[l] + numberAdded;
							}
							
							numberAdded++;
							
							break;
						}
					}
				}
			}
		}
		
		//create new buffers for store the old values plus the new values created by the critical pairs. 
		FloatBuffer[] newBuffers = new FloatBuffer[buffers.length];
		for(int i = 0; i < newBuffers.length; i++) {
			newBuffers[i] = BufferUtils.createFloatBuffer(buffers[i].capacity() + addingToBuffers[i].size());
			buffers[i].rewind();
			newBuffers[i].put(buffers[i]);
			
			for(float f: addingToBuffers[i]) {
				newBuffers[i].put(f);
			}
			
			buffers[i] = newBuffers[i];
			buffers[i].rewind();
		}
	}
	
	private static Source loadSoure(Element sourceElement) {
		Source source = new Source();
		source.id = sourceElement.getAttribute("id");
		
		String[] arrayElements = sourceElement.getElementsByTagName("float_array").item(0).getTextContent().split(" ");
		FloatBuffer buffer = BufferUtils.createFloatBuffer(arrayElements.length);
		
		for(int i = 0; i < arrayElements.length; i++) {
			buffer.put(i, Float.parseFloat(arrayElements[i]));
		}
		
		source.data = buffer;
		
		return source;
	}
	
	private static Source findSourceById(String id, Source[] sources) {
		for(Source s: sources) {
			if(s.id.equals(id)) {
				return s;
			}
		}
		
		return null;
	}
	
	private static ColladaMaterial findMaterialById(String id, ArrayList<ColladaMaterial> materials) {
		for(ColladaMaterial m: materials) {
			if(m.getId().equals(id)) {
				return m;
			}
		}
		
		return null;
	}
	
	private static ColladaImage findImageByID(String id, ArrayList<ColladaImage> images) {
		for(ColladaImage i: images) {
			if(i.getId().equals(id)) {
				return i;
			}
		}
		
		return null;
	}
	
	private static ColladaSurface findSurfaceByID(String id, ArrayList<ColladaSurface> surfaces) {
		for(ColladaSurface s: surfaces) {
			if(s.getId().equals(id)) {
				return s;
			}
		}
		
		return null;
	}
	
	private static ColladaTexture findTextureByID(String id, ArrayList<ColladaTexture> textures) {
		for(ColladaTexture t: textures) {
			if(t.getId().equals(id)) {
				return t;
			}
		}
		
		return null;
	}
	
	private static void multAll(FloatBuffer buffer, float[][] mat) {
		for(int i = 0; i < buffer.capacity(); i += 3) {
			int base = (i / 3) * 3;
			float x = mult(mat, buffer.get(base), buffer.get(base + 1), buffer.get(base + 2), 0);
			float y = mult(mat, buffer.get(base), buffer.get(base + 1), buffer.get(base + 2), 1);
			float z = mult(mat, buffer.get(base), buffer.get(base + 1), buffer.get(base + 2), 2);
			buffer.put(i, x);
			buffer.put(i + 1, y);
			buffer.put(i + 2, z);
		}
	}
	
	private static float mult(float[][] mat, float x, float y, float z, int coordinate) {
		float[] values = new float[]{x, y, z};
		float val = 0;
		
		for(int i = 0; i < mat[coordinate].length; i++) {
			val += mat[coordinate][i] * values[i];
		}
		
		return val;
	}
	
	private static int getTextureWrap(String text) {
		if("WRAP".equals(text)) {
			return GL_REPEAT;
		}
		
		if("MIRROR".equals(text)) {
			return GL14.GL_MIRRORED_REPEAT;
		}
		
		if("CLAMP".equals(text)) {
			return GL12.GL_CLAMP_TO_EDGE;
		}
		
		if("BORDER".equals(text)) {
			return GL13.GL_CLAMP_TO_BORDER;
		}
		
		if("NONE".equals(text)) {
			return GL13.GL_CLAMP_TO_BORDER;
		}
		
		return GL_REPEAT;
	}
	
	private static int getTextureFilter(String text) {
		if("NONE".equals(text)) {
			return GL_LINEAR;
		}
		
		if("NEAREST".equals(text)) {
			return GL_NEAREST;
		}
		
		if("LINEAR".equals(text)) {
			return GL_LINEAR;
		}
		
		if("NEAREST_MIPMAP_NEAREST".equals(text)) {
			return GL_NEAREST_MIPMAP_NEAREST;
		}
		
		if("LINEAR_MIPMAP_NEAREST".equals(text)) {
			return GL_LINEAR_MIPMAP_NEAREST;
		}
		
		if("NEAREST_MIPMAP_LINEAR".equals(text)) {
			return GL_NEAREST_MIPMAP_LINEAR;
		}
		
		if("LINEAR_MIPMAP_LINEAR".equals(text)) {
			return GL_LINEAR_MIPMAP_LINEAR;
		}
		
		return GL_LINEAR;
	}
	
	private static String getURL(File file, String path) {
		System.out.println("geturl: " + file.getAbsolutePath() + " ....  " + path);
		
		File newFile = new File(path);
		if(newFile.exists()) {
			return newFile.getAbsolutePath();
		}
		
		file = file.getParentFile();
		
		String[] split = path.split("/");
		int startIndex = 0;
		
		if(split[0].equals("..")) {
			file = file.getParentFile();
		} 
		
		if(split[0].equals(".")) {
			startIndex = 1;
		}
		
		StringBuffer buffer = new StringBuffer();
		buffer.append(file.getAbsolutePath());
		
		for(int i = startIndex; i < split.length; i++) {
			buffer.append(File.separator);
			buffer.append(split[i]);
		}
		
		return buffer.toString();
	}
	
	private static void loadScenes(Document document) {
//		ArrayList<Material> materials = new ArrayList<Material>();
//		
//		//find the library with the materials
//		NodeList libraryEffectsList = document.getElementsByTagName("library_effects");
//		
//		if(libraryEffectsList.getLength() > 0) {
//			//find any effect within the library
//			NodeList effectList = ((Element)libraryEffectsList.item(0)).getElementsByTagName("effect");
//			
//			//loop over the effects
//			for(int i = 0; i < effectList.getLength(); i++) {
//				Element effect = (Element)effectList.item(0);
//				String effectID = effect.getAttribute("id");
		//find the library
		NodeList libraryVisualScenesList = document.getElementsByTagName("library_visual_scenes");
		
		if(libraryVisualScenesList.getLength() > 0) {
			//locate the visual scene lists
			NodeList visualScenesList = ((Element)libraryVisualScenesList.item(0)).getElementsByTagName("visual_scene");
			
			//loop over the visual scenes
			for(int i = 0; i < visualScenesList.getLength(); i++) {
				
			}
		}
	}
	
	private static class Source {
		private String id;
		private FloatBuffer data;
	}
}
